﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;

public class MyUdpServerClientConn
{
    private const bool Log_Enable = true;

    private readonly MyUdpServerConn m_ServerConn;
    private readonly Socket m_UdpSocket;
    public readonly EndPoint m_EndPoint;
    private bool m_IsStopping;

    private Dictionary<ushort, MyUdpPacket> m_RecvDict = new Dictionary<ushort, MyUdpPacket>();

    public long m_LastRecvTime;
    private long m_LastPingTime;
    public int m_PingNum; //Ping计数, 成功Recv则重置为0


    private bool m_BeginSendRunning;
    private ushort m_SendId = 0;

    private Queue<SendData> m_SendQueue = new Queue<SendData>();

    private Dictionary<ushort, MyUdpPacket> m_WaitRemoteResponseDict = new Dictionary<ushort, MyUdpPacket>();
    private List<MyUdpPacket> m_WaitRemoteResponseList = new List<MyUdpPacket>();

    private MyUdpPacket m_SendPacket;
    private int m_SendSliceIndex;

    private byte[] m_TempSendData = new byte[UdpHelper.UDP_Slice_Head_Len];

    private byte[] m_SendData;
    private int m_SendDataLen;
    private int m_HaveSendLen;


    public MyUdpServerClientConn(MyUdpServerConn conn, Socket socket, EndPoint ep)
    {
        m_ServerConn = conn;
        m_UdpSocket = socket;
        m_EndPoint = ep;
    }

    public bool IsStopped
    {
        get { return m_IsStopping && !m_BeginSendRunning; }
    }

    public void Stop()
    {
        if (m_IsStopping) return;
        m_IsStopping = true;
    }

    //自己发的消息对方长时间没有回应, 会重发
    public void CheckReSend()
    {
        if (m_IsStopping) return;

        long now = SocketHelper.CurrentTimeMillis();
        int nullElementCount = 0;
        lock (m_WaitRemoteResponseDict)
        {
            for (int i = 0; i < m_WaitRemoteResponseList.Count; ++i)
            {
                var udpPacket = m_WaitRemoteResponseList[i];
                if (null == udpPacket)
                {
                    nullElementCount++;
                    continue; //todo: null元素移到最后
                }

                int notReadyNum = 0;
                int retryFailNum = 0;
                for (int j = 0; j < udpPacket.m_Slices.Length; ++j)
                {
                    var slice = udpPacket.m_Slices[j];
                    if (null == slice || slice.m_IsRemoteResp)
                    {
                        continue;
                    }

                    notReadyNum++;
                    long elapseTime = now - slice.m_LastSentTime;
                    if (elapseTime >= 5 * 1000) //重发
                    {
                        if (slice.m_ReSendCount >= 5)
                        {
                            retryFailNum++; //todo: 通知重发失败
                            Debug.LogError($"{m_EndPoint}: reSend fail: {udpPacket.GetMsgId()}");
                        }
                        else
                        {
                            slice.m_ReSendCount++;
                            slice.m_LastSentTime = now;
                            AddSendSlice(slice);
                        }
                    }
                }

                if (notReadyNum <= 0 || retryFailNum > 0)
                {
                    m_WaitRemoteResponseList[i] = null;

                    m_WaitRemoteResponseDict.Remove(udpPacket.GetMsgId());
                }
            }

            float nullPrg = (float)nullElementCount / m_WaitRemoteResponseList.Count;
            if (nullPrg >= 0.2f)
            {
                SocketHelper.RemoveNullElements(m_WaitRemoteResponseList);
            }
        }
    }

    public void CheckPing()
    {
        if (m_IsStopping) return;

        long now = SocketHelper.CurrentTimeMillis();
        long recvElapseTime = now - m_LastRecvTime;
        if (recvElapseTime < 20 * 1000) return;

        long pingElapseTime = now - m_LastPingTime;
        if (pingElapseTime < 1000) return;

        m_LastPingTime = now;
        if (m_PingNum >= 3)
        {
            //第3次Ping没回应时, 认为与服务器断开
            Debug.LogError($"{m_EndPoint}: have disconnect: Ping Fail, {m_PingNum}");
            return;
        }
        
        if (Log_Enable) Debug.Log($"ServerClient: {m_EndPoint}: Ping: num:{m_PingNum}, time:{pingElapseTime}");
        m_PingNum++;
        //msgId: 2-byte
        m_TempSendData[0] = 0;
        m_TempSendData[1] = 0;
        //opCode: 1-byte
        m_TempSendData[2] = 2;
        //bodyLen: 1-byte
        m_TempSendData[3] = 0;

        int v = BitConverter.ToInt32(m_TempSendData, 0);
        AddSendV(v);
    }


    //******************** 接收

    public void ParseRecvData(Socket socket, byte[] buff, int readedLen, int opCode, int bodyLen)
    {
        ushort msgId = BitConverter.ToUInt16(buff, 0);
        int sliceIndex = BitConverter.ToInt16(buff, 4);
        int sliceCount = BitConverter.ToInt16(buff, 6);
        if (Log_Enable) Debug.Log($"ServerClient: recv slice ready: msgId:{msgId}, opCode:{opCode}, bodyLen:{bodyLen} <-{m_EndPoint}");

        switch (opCode)
        {
        case 0: //普通包
            {
                //要给发送方一个回应
                lock (m_SendQueue)
                {
                    int v = BitConverter.ToInt32(buff, 0);
                    AddSendV(v);
                }

                if (!m_RecvDict.TryGetValue(msgId, out var udpPacket)) //第1次收到
                {
                    udpPacket = new MyUdpPacket();
                    udpPacket.m_Slices = new MyUdpSlice[sliceCount];
                    m_RecvDict.Add(msgId, udpPacket);
                }

                if (sliceIndex < 0 || sliceIndex >= udpPacket.m_Slices.Length)
                {
                    Debug.LogWarning($"ServerClient: recv client invalid sliceIndex:{sliceIndex} <-{m_EndPoint}");
                    return;
                }

                var slice = udpPacket.m_Slices[sliceIndex];
                if (null == slice)
                {
                    slice = new MyUdpSlice();
                    udpPacket.m_Slices[sliceIndex] = slice;
                }

                slice.m_BodyLen = bodyLen;
                Buffer.BlockCopy(buff, readedLen, slice.m_Data, 0, UdpHelper.UDP_Slice_Head_Len + bodyLen);
                if (udpPacket.CheckAllReady(out var str))
                {
                    var msg = new SocketHelper.ThreadMessage();
                    msg.m_Cmd = "Server.Packet";
                    msg.m_Content = str;
                    msg.m_Obj = this;
                    m_ServerConn.EnqueueMessage(msg);

                    m_RecvDict.Remove(msgId);
                }
            }
            break;

        case 1: //client回应server数据已收到
            {
                if (Log_Enable) Debug.Log($"ServerClient: client recvSucc: <-{m_EndPoint}");
                lock (m_WaitRemoteResponseDict)
                {
                    if (m_WaitRemoteResponseDict.TryGetValue(msgId, out var packet))
                    {
                        if (sliceIndex < 0 || sliceIndex >= packet.m_Slices.Length)
                        {
                            Debug.LogWarning($"ServerClient: invalid sliceIndex:{sliceIndex} <-{m_EndPoint}");
                            return;
                        }
                        else
                        {
                            var slice = packet.m_Slices[sliceIndex];
                            slice.m_IsRemoteResp = true;
                        }
                    }
                    else
                    {
                        Debug.LogWarning($"ServerClient: invalid msgId:{msgId} <-{m_EndPoint}");
                    }
                }
            }
            break;

        case 2: //心跳包Ping
            {
                if (Log_Enable) Debug.Log($"ServerClient: recv client Ping <-{m_EndPoint}");
                //回应Pong
                int v = BitConverter.ToInt32(buff, 0);
                AddSendV(v);
            }
            break;
        }
    }

    //******************** 

    //******************** 发送

    public void Send(string str)
    {
        var sendData = new SendData();
        sendData.m_CreateTime = SocketHelper.CurrentTimeMillis();
        sendData.m_Str = str;

        lock (m_SendQueue) //检查是否再发其他的还要
        {
            m_SendQueue.Enqueue(sendData);
            if (m_SendQueue.Count > 1)
                return;
        }

        m_BeginSendRunning = true;
        SendStr(m_UdpSocket, str);
    }

    private void AddSendV(int v)
    {
        var sendData = new SendData();
        sendData.m_CreateTime = SocketHelper.CurrentTimeMillis();
        sendData.m_V = v;

        lock (m_SendQueue) //检查是否再发其他的还要
        {
            m_SendQueue.Enqueue(sendData);
            if (m_SendQueue.Count > 1)
                return;
        }

        m_BeginSendRunning = true;
        SendData2(m_UdpSocket, v);
    }

    private void AddSendSlice(MyUdpSlice slice)
    {
        var sendData = new SendData();
        sendData.m_CreateTime = SocketHelper.CurrentTimeMillis();
        sendData.m_Slice = slice;

        lock (m_SendQueue) //检查是否再发其他的还要
        {
            m_SendQueue.Enqueue(sendData);
            if (m_SendQueue.Count > 1)
                return;
        }

        m_BeginSendRunning = true;
        SendSlice(m_UdpSocket, slice, UdpHelper.UDP_Slice_Head_Len + slice.m_BodyLen);
    }

    private void CheckQueueNext(Socket socket)
    {
        if (m_IsStopping)
        {
            if (Log_Enable) Debug.Log($"ServerClient: BeginSend exit: stopping, tid:{Thread.CurrentThread.ManagedThreadId} ->{m_EndPoint}");
            m_BeginSendRunning = false;
            return;
        }

        SendData sendData;// = new SendData();
        lock (m_SendQueue)
        {
            m_SendQueue.Dequeue();
            if (m_SendQueue.Count <= 0)
            {
                m_BeginSendRunning = false;
                if (Log_Enable) Debug.Log($"ServerClient: BeginSend exit: no data, tid:{Thread.CurrentThread.ManagedThreadId}");
                return;
            }

            sendData = m_SendQueue.Peek();
        }
        NextSend(socket, sendData);
    }

    private void NextSend(Socket socket, SendData sendData)
    {
        if (string.IsNullOrEmpty(sendData.m_Str))
        {
            SendStr(socket, sendData.m_Str);
        }
        else if (0 != sendData.m_V)
        {
            SendData2(socket, sendData.m_V);
        }
        else if (null != sendData.m_Slice)
        {
            SendSlice(socket, sendData.m_Slice, UdpHelper.UDP_Slice_Head_Len + sendData.m_Slice.m_BodyLen);
        }
        else
        {
            //???
        }
    }

    private void SendStr(Socket socket, string str)
    {
        var strBytes = Encoding.UTF8.GetBytes(str);
        int strLen = strBytes.Length;
        int remainStrLen = strLen;

        //分包
        var packet = new MyUdpPacket();
        int sliceCount = Mathf.CeilToInt((float)strLen / UdpHelper.UDP_Slice_Max_Body_Size);
        packet.m_Slices = new MyUdpSlice[sliceCount];
        short msgId = (short)m_SendId;
        for (int i = 0; i < sliceCount; ++i)
        {
            var slice = new MyUdpSlice();
            packet.m_Slices[i] = slice;

            int bodyLen = Mathf.Min(UdpHelper.UDP_Slice_Max_Body_Size, remainStrLen);
            slice.m_BodyLen = bodyLen;
            //包头
            SocketHelper.ConvertInt16To2Bytes(msgId, slice.m_Data, 0);
            slice.m_Data[2] = 0; //普通消息
            slice.m_Data[3] = (byte)(strLen & 0xff);
            SocketHelper.ConvertInt16To2Bytes((short)i, slice.m_Data, 4);
            SocketHelper.ConvertInt16To2Bytes((short)sliceCount, slice.m_Data, 6);
            //消息体
            Buffer.BlockCopy(strBytes, strLen - remainStrLen, slice.m_Data, UdpHelper.UDP_Slice_Head_Len, bodyLen);

            remainStrLen -= UdpHelper.UDP_Slice_Max_Body_Size;
        }
        m_SendPacket = packet;
        m_SendSliceIndex = 0;
        m_SendId++;

        lock (m_WaitRemoteResponseDict)
        {
            m_WaitRemoteResponseList.Add(packet);
            m_WaitRemoteResponseDict.Add(m_SendId, packet);
        }

        var sendSlice = m_SendPacket.m_Slices[m_SendSliceIndex];
        m_HaveSendLen = 0;
        if (Log_Enable) Debug.Log($"ServerClient: slice:{m_SendSliceIndex} startSend ->{m_EndPoint}");
        SendSlice(socket, sendSlice, UdpHelper.UDP_Slice_Head_Len + sendSlice.m_BodyLen);
    }

    private void SendSlice(Socket socket, MyUdpSlice sendSlice, int dataLen)
    {
        sendSlice.m_LastSentTime = SocketHelper.CurrentTimeMillis();
        try
        {
            int notSendLen = dataLen - m_HaveSendLen;
            socket.BeginSendTo(sendSlice.m_Data, m_HaveSendLen, notSendLen, 0, m_EndPoint, OnBeginSendResult, socket);
        }
        catch (Exception ex)
        {
            if (Log_Enable) Debug.LogError($"ServerClient: {m_EndPoint}, connected:{socket.Connected}, {ex.GetType().Name}, {ex.Message}");
        }
    }

    private void OnBeginSendResult(IAsyncResult ar)
    {
        var socket = (Socket)ar.AsyncState;
        try
        {
            int realSendLen = socket.EndSendTo(ar);
            if (realSendLen <= 0) //数据发不出去, 断开了?
            {
                Debug.LogError($"ServerClient: sendLen <= 0, ->{m_EndPoint}");
            }
            else
            {
                m_HaveSendLen += realSendLen;

                SendData topSendData;
                lock (m_SendQueue)
                    topSendData = m_SendQueue.Peek();

                if (!string.IsNullOrEmpty(topSendData.m_Str))
                {
                    OnSendUdpPacket(socket);
                }
                else if (0 != topSendData.m_V)
                {
                    OnSendData(socket);
                }
                else if (null != topSendData.m_Slice)
                {
                    OnSendSlice(socket, topSendData.m_Slice);
                }
                else
                {
                    //??? 发的unknown数据?
                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError($"ServerClient: {m_EndPoint}: {ex.GetType().Name}:{ex.Message}");
        }
    }

    private void OnSendUdpPacket(Socket socket)
    {
        var sendSlice = m_SendPacket.m_Slices[m_SendSliceIndex];
        int dataLen = UdpHelper.UDP_Slice_Head_Len + sendSlice.m_BodyLen;
        int notSendLen = dataLen - m_HaveSendLen;
        if (notSendLen > 0)
        {
            //todo: 把下一个数据包的数据凑过来发送
            sendSlice.m_LastSentTime = SocketHelper.CurrentTimeMillis();
            socket.BeginSendTo(sendSlice.m_Data, m_HaveSendLen, notSendLen, 0, m_EndPoint, OnBeginSendResult, socket);
        }
        else
        {
            m_SendSliceIndex++;
            if (m_SendSliceIndex < m_SendPacket.m_Slices.Length)
            {
                sendSlice = m_SendPacket.m_Slices[m_SendSliceIndex];
                sendSlice.m_LastSentTime = SocketHelper.CurrentTimeMillis();
                m_HaveSendLen = 0;
                notSendLen = (UdpHelper.UDP_Slice_Head_Len + sendSlice.m_BodyLen) - m_HaveSendLen;

                if (Log_Enable) Debug.Log($"ServerClient: slice:{m_SendSliceIndex} startSend ->{m_EndPoint}");
                socket.BeginSendTo(sendSlice.m_Data, m_HaveSendLen, notSendLen, 0, m_EndPoint, OnBeginSendResult, socket);
            }
            else
            {
                m_SendPacket = null;
                CheckQueueNext(socket);
            }
        }
    }

    private void OnSendSlice(Socket socket, MyUdpSlice sendSlice)
    {
        int dataLen = UdpHelper.UDP_Slice_Head_Len + sendSlice.m_BodyLen;
        int notSendLen = dataLen - m_HaveSendLen;
        if (notSendLen > 0)
        {
            //todo: 把下一个数据包的数据凑过来发送
            sendSlice.m_LastSentTime = SocketHelper.CurrentTimeMillis();
            socket.BeginSendTo(sendSlice.m_Data, m_HaveSendLen, notSendLen, 0, m_EndPoint, OnBeginSendResult, socket);
        }
        else
        {
            CheckQueueNext(socket);
        }
    }

    private void OnSendData(Socket socket)
    {
        int notSendLen = m_SendDataLen - m_HaveSendLen;
        if (notSendLen > 0)
        {
            //todo: 把下一个数据包的数据凑过来发送
            socket.BeginSendTo(m_SendData, m_HaveSendLen, notSendLen, 0, m_EndPoint, OnBeginSendResult, socket);
        }
        else
        {
            m_SendData = null;
            //前一个packet已发完: 检查是否有回应数据要发(已收到, Pong); 没收到回应的重发; 心跳;
            CheckQueueNext(socket);
        }
    }



    //别人发过来的消息要回应, 比如: 消息已收到, PONG
    private void SendData2(Socket socket, int v)
    {
        SocketHelper.ConvertInt32To4Bytes(v, m_TempSendData, 0);
        switch (m_TempSendData[2]) //opCode
        {
        case 0: //普通包回应已收到
            m_TempSendData[2] = 1;
            break;

        case 2: //Ping包回应Pong
            m_TempSendData[2] = 3;
            break;
        }

        m_TempSendData[3] = 0;
        //sliceIndex
        m_TempSendData[4] = 0;
        m_TempSendData[5] = 0;
        //sliceCount
        m_TempSendData[6] = 0;
        m_TempSendData[7] = 0;

        m_SendData = m_TempSendData;
        m_SendDataLen = m_TempSendData.Length;
        m_HaveSendLen = 0;

        ushort msgId = BitConverter.ToUInt16(m_TempSendData, 0);
        if (Log_Enable) Debug.Log($"ServerClient: send response: opCode:{m_TempSendData[2]}, msgId:{msgId} ->{m_EndPoint}");

        try
        {
            int notSendLen = m_SendDataLen - m_HaveSendLen;
            socket.BeginSendTo(m_SendData, m_HaveSendLen, notSendLen, 0, m_EndPoint, OnBeginSendResult, socket);
        }
        catch (Exception ex)
        {
            Debug.LogError($"ServerClient: {m_EndPoint}: {ex.GetType().Name}:{ex.Message}");
        }
    }

}
